"use client"

import {
  type ColumnFiltersState,
  type PaginationState,
  type RowSelectionState,
  type SortingState,
  type TableOptions,
  type TableState,
  type Updater,
  type VisibilityState,
  getCoreRowModel,
  getFacetedRowModel,
  getFacetedUniqueValues,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
} from "@tanstack/react-table"
import {
  type Parser,
  type UseQueryStateOptions,
  parseAsArrayOf,
  parseAsInteger,
  parseAsString,
  useQueryState,
  useQueryStates,
} from "nuqs"
import * as React from "react"
import type { DataTableFilterField, ExtendedSortingState } from "~/types"

import { useDebouncedCallback } from "@mantine/hooks"
import { useMemo, useState } from "react"
import { getSortingStateParser } from "~/lib/parsers"

type UseDataTableProps<TData> = Omit<
  TableOptions<TData>,
  | "state"
  | "pageCount"
  | "getCoreRowModel"
  | "manualFiltering"
  | "manualPagination"
  | "manualSorting"
> &
  Required<Pick<TableOptions<TData>, "pageCount">> & {
    /**
     * Defines filter fields for the table. Supports both dynamic faceted filters and search filters.
     * - Faceted filters are rendered when `options` are provided for a filter field.
     * - Otherwise, search filters are rendered.
     *
     * The indie filter field `value` represents the corresponding column name in the database table.
     * @default []
     * @type { label: string, value: keyof TData, placeholder?: string, options?: { label: string, value: string, icon?: ComponentType<{ className?: string }> }[] }[]
     * @example
     * ```ts
     * // Render a search filter
     * const filterFields = [
     *   { label: "Title", value: "title", placeholder: "Search titles" }
     * ];
     * // Render a faceted filter
     * const filterFields = [
     *   {
     *     label: "Status",
     *     value: "status",
     *     options: [
     *       { label: "Todo", value: "todo" },
     *       { label: "In Progress", value: "in-progress" },
     *     ]
     *   }
     * ];
     * ```
     */
    filterFields?: DataTableFilterField<TData>[]

    /**
     * Determines how query updates affect history.
     * `push` creates a new history entry; `replace` (default) updates the current entry.
     * @default "replace"
     */
    history?: "push" | "replace"

    /**
     * Indicates whether the page should scroll to the top when the URL changes.
     * @default false
     */
    scroll?: boolean

    /**
     * Shallow mode keeps query states client-side, avoiding server calls.
     * Setting to `false` triggers a network request with the updated querystring.
     * @default true
     */
    shallow?: boolean

    /**
     * Maximum time (ms) to wait between URL query string updates.
     * Helps with browser rate-limiting. Minimum effective value is 50ms.
     * @default 50
     */
    throttleMs?: number

    /**
     * Debounce time (ms) for filter updates to enhance performance during rapid input.
     * @default 300
     */
    debounceMs?: number

    /**
     * Observe Server Component loading states for non-shallow updates.
     * Pass `startTransition` from `useTransition()`.
     * Sets `shallow` to `false` automatically.
     * So shallow: true` and `startTransition` cannot be used at the same time.
     * @see https://dev/reference/react/useTransition
     */
    startTransition?: React.TransitionStartFunction

    /**
     * Clear URL query key-value pair when state is set to default.
     * Keep URL meaning consistent when defaults change.
     * @default false
     */
    clearOnDefault?: boolean

    /**
     * Enable notion like column filters.
     * Advanced filters and column filters cannot be used at the same time.
     * @default false
     * @type boolean
     */
    enableAdvancedFilter?: boolean

    initialState?: Omit<Partial<TableState>, "sorting"> & {
      // Extend to make the sorting id typesafe
      sorting?: ExtendedSortingState<TData>
    }
  }

export function useDataTable<TData>({
  pageCount = -1,
  filterFields = [],
  enableAdvancedFilter = false,
  history = "replace",
  scroll = false,
  shallow = true,
  throttleMs = 50,
  debounceMs = 250,
  clearOnDefault = false,
  startTransition,
  initialState,
  ...props
}: UseDataTableProps<TData>) {
  const queryStateOptions = useMemo<Omit<UseQueryStateOptions<string>, "parse">>(
    () => ({
      history,
      scroll,
      shallow,
      throttleMs,
      debounceMs,
      clearOnDefault,
      startTransition,
    }),
    [history, scroll, shallow, throttleMs, debounceMs, clearOnDefault, startTransition],
  )

  const [rowSelection, setRowSelection] = useState<RowSelectionState>(
    initialState?.rowSelection ?? {},
  )
  const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
    initialState?.columnVisibility ?? {},
  )

  const [page, setPage] = useQueryState(
    "page",
    parseAsInteger.withOptions(queryStateOptions).withDefault(1),
  )
  const [perPage, setPerPage] = useQueryState(
    "perPage",
    parseAsInteger
      .withOptions(queryStateOptions)
      .withDefault(initialState?.pagination?.pageSize ?? 25),
  )
  const [sorting, setSorting] = useQueryState(
    "sort",
    getSortingStateParser<TData>()
      .withOptions(queryStateOptions)
      .withDefault(initialState?.sorting ?? []),
  )

  // Create parsers for each filter field
  const filterParsers = useMemo(() => {
    return filterFields.reduce<Record<string, Parser<string> | Parser<string[]>>>((acc, field) => {
      if (field.options) {
        // Faceted filter
        acc[field.id] = parseAsArrayOf(parseAsString, ",").withOptions(queryStateOptions)
      } else {
        // Search filter
        acc[field.id] = parseAsString.withOptions(queryStateOptions)
      }
      return acc
    }, {})
  }, [filterFields, queryStateOptions])

  const [filterValues, setFilterValues] = useQueryStates(filterParsers)

  const debouncedSetFilterValues = useDebouncedCallback((values: typeof filterValues) => {
    void setPage(1)
    void setFilterValues(values)
  }, debounceMs)

  // Paginate
  const pagination: PaginationState = {
    pageIndex: page - 1, // zero-based index -> one-based index
    pageSize: perPage,
  }

  function onPaginationChange(updaterOrValue: Updater<PaginationState>) {
    if (typeof updaterOrValue === "function") {
      const newPagination = updaterOrValue(pagination)
      void setPage(newPagination.pageIndex + 1)
      void setPerPage(newPagination.pageSize)
    } else {
      void setPage(updaterOrValue.pageIndex + 1)
      void setPerPage(updaterOrValue.pageSize)
    }
  }

  // Sort
  function onSortingChange(updaterOrValue: Updater<SortingState>) {
    if (typeof updaterOrValue === "function") {
      const newSorting = updaterOrValue(sorting) as ExtendedSortingState<TData>
      void setSorting(newSorting)
    }
  }

  // Filter
  const initialColumnFilters: ColumnFiltersState = useMemo(() => {
    return enableAdvancedFilter
      ? []
      : Object.entries(filterValues).reduce<ColumnFiltersState>((filters, [key, value]) => {
          if (value !== null) {
            filters.push({
              id: key,
              value: Array.isArray(value) ? value : [value],
            })
          }
          return filters
        }, [])
  }, [filterValues, enableAdvancedFilter])

  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(initialColumnFilters)

  // Memoize computation of searchableColumns and filterableColumns
  const { searchableColumns, filterableColumns } = useMemo(() => {
    return enableAdvancedFilter
      ? { searchableColumns: [], filterableColumns: [] }
      : {
          searchableColumns: filterFields.filter(field => !field.options),
          filterableColumns: filterFields.filter(field => field.options),
        }
  }, [filterFields, enableAdvancedFilter])

  const onColumnFiltersChange = React.useCallback(
    (updaterOrValue: Updater<ColumnFiltersState>) => {
      // Don't process filters if advanced filtering is enabled
      if (enableAdvancedFilter) return

      setColumnFilters(prev => {
        const next = typeof updaterOrValue === "function" ? updaterOrValue(prev) : updaterOrValue

        const filterUpdates = next.reduce<Record<string, string | string[] | null>>(
          (acc, filter) => {
            if (searchableColumns.find(col => col.id === filter.id)) {
              // For search filters, use the value directly
              acc[filter.id] = filter.value as string
            } else if (filterableColumns.find(col => col.id === filter.id)) {
              // For faceted filters, use the array of values
              acc[filter.id] = filter.value as string[]
            }
            return acc
          },
          {},
        )

        for (const prevFilter of prev) {
          if (!next.some(filter => filter.id === prevFilter.id)) {
            filterUpdates[prevFilter.id] = null
          }
        }

        debouncedSetFilterValues(filterUpdates)
        return next
      })
    },
    [debouncedSetFilterValues, enableAdvancedFilter, filterableColumns, searchableColumns, setPage],
  )

  const table = useReactTable({
    ...props,
    initialState,
    pageCount,
    state: {
      pagination,
      sorting,
      columnVisibility,
      rowSelection,
      columnFilters: enableAdvancedFilter ? [] : columnFilters,
    },
    defaultColumn: {
      size: 0,
      minSize: 0,
    },
    enableRowSelection: true,
    onRowSelectionChange: setRowSelection,
    onPaginationChange,
    onSortingChange,
    onColumnFiltersChange,
    onColumnVisibilityChange: setColumnVisibility,
    getCoreRowModel: getCoreRowModel(),
    getFilteredRowModel: enableAdvancedFilter ? undefined : getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFacetedRowModel: enableAdvancedFilter ? undefined : getFacetedRowModel(),
    getFacetedUniqueValues: enableAdvancedFilter ? undefined : getFacetedUniqueValues(),
    manualPagination: true,
    manualSorting: true,
    manualFiltering: true,
  })

  return { table }
}
